package org.digitarts.rtms.spring.collectors.shell.ssh;

import java.io.ByteArrayOutputStream;
import java.net.URL;
import java.util.Map;

import org.apache.log4j.Logger;
import org.digitarts.rtms.spring.collectors.shell.IRemoteShell;

import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.UserInfo;

public class JSchRemoteShell implements IRemoteShell, UserInfo
{
	protected String	userName			= null;
	protected String	hostName			= null;
	protected int		port				= 22;
	protected URL		knownHostsFileUrl	= null;
	protected URL		privateKeyUrl		= null;
	protected String	passphrase			= null;
	protected int		timeOut				= 10000;

	protected Logger	log					= Logger.getLogger(JSchRemoteShell.class);

	/** The JSch SSH Provider */
	protected JSch		jsch				= new JSch();
	/** The JSch Session */
	protected Session	session				= null;

	public String issueOSCommand(String command, Map<String, ?>... context) throws Exception
	{
		return issueOSCommand(command, timeOut, context);
	}

	public String issueOSCommand(String command, long commandTimeOut, Map<String, ?>... context) throws Exception
	{
		try
		{
			final ChannelExec exec = (ChannelExec) session.openChannel("exec");
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			exec.setCommand(command);
			exec.setOutputStream(out);
			exec.setExtOutputStream(out);
			exec.connect();
			final Thread thread = new Thread()
			{
				public void run()
				{
					while (!exec.isEOF())
					{
						try
						{
							Thread.sleep(500L);
						}
						catch (Exception e)
						{
							e.printStackTrace();
						}
					}
				}
			};
			thread.setDaemon(true);
			thread.start();
			thread.join(commandTimeOut);
			thread.interrupt();
			if (thread.isAlive())
			{
				throw new Exception("Time Out Error");
			}
			else
			{
				try
				{
					exec.disconnect();
				}
				catch (Exception e)
				{
				}
				return out.toString();
			}
		}
		catch (Throwable e)
		{
			e.printStackTrace();
			return e.getMessage();
		}
	}

	public void close()
	{
		try
		{
			this.session.disconnect();
		}
		catch (Exception e)
		{
		}
		log.info("Remote IShell to [" + hostName + "] closed");
	}

	public void init() throws Exception
	{
		jsch.setKnownHosts(knownHostsFileUrl.openStream());
		jsch.addIdentity(privateKeyUrl.getFile(), passphrase);
		session = connectSSH();
		log.info("Remote IShell to [" + hostName + "] initialized");
		if (log.isDebugEnabled())
			log.debug(this.toString());
	}

	/**
	 * Connects to the SSH Server.
	 * 
	 * @throws Exception
	 */
	protected Session connectSSH() throws Exception
	{
		try
		{
			Session session = jsch.getSession(userName, hostName, port);
			session.setUserInfo(this);
			session.connect(timeOut);
			return session;
		}
		catch (Exception e)
		{
			log.error("Failed to connect to server", e);
			throw new Exception("Failed to connect to server", e);
		}
	}

	/**
	 * @return the userName
	 */
	public String getUserName()
	{
		return userName;
	}

	/**
	 * @param userName
	 *            the userName to set
	 */
	public void setUserName(String userName)
	{
		this.userName = userName;
	}

	/**
	 * @return the hostName
	 */
	public String getHostName()
	{
		return hostName;
	}

	/**
	 * @param hostName
	 *            the hostName to set
	 */
	public void setHostName(String hostName)
	{
		this.hostName = hostName;
		log.debug("Host Name Set:[" + hostName + "]");
	}

	/**
	 * @return the port
	 */
	public int getPort()
	{
		return port;
	}

	/**
	 * @param port
	 *            the port to set
	 */
	public void setPort(int port)
	{
		this.port = port;
	}

	/**
	 * @return the knownHostsFile
	 */
	public URL getKnownHostsFileUrl()
	{
		return knownHostsFileUrl;
	}

	/**
	 * @param knownHostsFileUrl
	 *            the knownHostsFileUrl to set
	 */
	public void setKnownHostsFileUrl(URL knownHostsFileUrl)
	{
		this.knownHostsFileUrl = knownHostsFileUrl;
	}

	/**
	 * @return the privateKeyUrl
	 */
	public URL getPrivateKeyUrl()
	{
		return privateKeyUrl;
	}

	/**
	 * @param privateKeyUrl
	 *            the privateKeyUrl to set
	 */
	public void setPrivateKeyUrl(URL privateKeyUrl)
	{
		this.privateKeyUrl = privateKeyUrl;
	}

	/**
	 * @param passphrase
	 *            the passphrase to set
	 */
	public void setPassphrase(String passphrase)
	{
		this.passphrase = passphrase;
	}

	public String getPassphrase()
	{
		return passphrase;
	}

	public String getPassword()
	{
		return null;
	}

	public boolean promptPassphrase(String arg0)
	{
		return false;
	}

	public boolean promptPassword(String arg0)
	{
		return false;
	}

	public boolean promptYesNo(String arg0)
	{
		return false;
	}

	public void showMessage(String arg0)
	{
	}

	/**
	 * @return the timeOut
	 */
	public int getTimeOut()
	{
		return timeOut;
	}

	/**
	 * @param timeOut
	 *            the timeOut to set
	 */
	public void setTimeOut(int timeOut)
	{
		this.timeOut = timeOut;
	}

	/**
	 * Constructs a <code>String</code> with all attributes in name = value
	 * format.
	 * 
	 * @return a <code>String</code> representation of this object.
	 */
	public String toString()
	{
		final String TAB = "    \n\t";

		StringBuilder retValue = new StringBuilder();

		retValue.append("JSchRemoteShell ( ").append(super.toString()).append(TAB).append("userName = ").append(this.userName).append(TAB).append("hostName = ").append(this.hostName).append(TAB)
				.append("port = ").append(this.port).append(TAB).append("knownHostsFile = ").append(this.knownHostsFileUrl).append(TAB).append("privateKey = ").append(this.privateKeyUrl).append(TAB)
				.append("passphrase = ").append(this.passphrase).append(TAB).append("timeOut = ").append(this.timeOut).append(TAB).append(" )");

		return retValue.toString();
	}

}
